/**
 * 
 */
package util;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkPositionIndex;
import static com.google.common.base.Preconditions.checkState;

import java.util.AbstractSequentialList;
import java.util.ListIterator;


/**
 * @author Joshua Lockerman
 * @param <E> 
 *
 */
public class ExposedLinkedList<E> extends AbstractSequentialList<E> {
    
    private final Node head;
    
    private final Node tail;
    
    private int size;
    /**
     * 
     */
    public ExposedLinkedList()
    {
        head = new Node();
        tail = new Node();
        head.next = tail;
        tail.previous = head;
        size = 0;
    }
    @Override
    public boolean add(E e)
    {
        checkNotNull(e);
        return tail.addBefore(e) != null;
    }
    
    @Override
    public void add(int index, E element)
    {
        checkPositionIndex(index, size);
        checkNotNull(element);
        Iterator it = new Iterator(index);
        it.add(element);
    }
    
    public Node addElement(E e)
    {
        checkNotNull(e);
        return tail.addBefore(e);
    }
    
    @Override
    public int indexOf(Object o)
    {
        checkNotNull(o);
        return super.indexOf(o);
    }
    
    @Override
    public int lastIndexOf(Object o)
    {
        checkNotNull(o);
        return super.lastIndexOf(o);
    }
    
    /* (non-Javadoc)
     * @see java.util.AbstractSequentialList#listIterator(int)
     */
    @Override
    public ListIterator<E> listIterator(int index)
    {
        checkPositionIndex(index, size);
        return new Iterator(index);
    }
    
    @Override
    public boolean remove(Object o)
    {
        checkNotNull(o);
        Iterator it = new Iterator();
        boolean removed = false;
        while(it.hasNext() && !removed) {
            if(o.equals(it.next())) {
                it.removePrevious();
                removed = true;
            }
        }
        return removed;
    }
    
    @Override
    public E set(int index, E element)
    {
        checkPositionIndex(index, size);
        checkNotNull(element);
        Iterator it = new Iterator(index);
        return it.setNext(element);
    }
    
    /* (non-Javadoc)
     * @see java.util.AbstractCollection#size()
     */
    @Override
    public int size()
    {
        return size;
    }
    
    public class Node {
        
        private final E data;
        private Node previous;
        private Node next;
        
        private Node()
        {
            data = null;
            previous = next = this;
        }
        
        private Node(E e, Node toReplace)
        {
            data = e;
            this.previous = toReplace.previous;
            this.next = toReplace.next;
            previous.next = this;
            next.previous = this;
            toReplace.next = null;
            toReplace.previous = null;
        }
        
        private Node(E e, Node previous, Node next)
        {
            data = e;
            this.previous = previous;
            this.next = next;
            previous.next = this;
            next.previous = this;
            size++;
        }
        
        public boolean isPartOfList()
        {
            return previous != null && next != null;
        }
        
        public void remove()
        {
            if(isPartOfList()) {
                previous.next = this.next;
                next.previous = this.previous;
                this.next = this.previous = null;
                size--;
            }
        }
        
        private Node addAfter(E e)
        {
            return new Node(e, this, next);
        }
        
        private Node addBefore(E e)
        {
            return new Node(e, previous, this);
        }
        
        private E getData()
        {
            return data;
        }
    }
    
    private class Iterator implements ListIterator<E> {
        
        private Node previous;
        private int previousIndex;
        
        private Iterator()
        {
            previous = head;
            previousIndex = -1;
        }
        
        private Iterator(int index)
        {
            this();
            checkPositionIndex(index, size);
            while(previousIndex <= index - 1) {
                next();
            }
        }
        
        @Override
        public void add(E e)
        {
            previous.addAfter(e);
        }
        
        @Override
        public boolean hasNext()
        {
            return previous.next != tail;
        }
        
        @Override
        public boolean hasPrevious()
        {
            return previous != head && previous != tail;
        }
        
        @Override
        public E next()
        {
            if(previous != tail) {
                previousIndex++;
            }
            previous = previous.next;
            return previous.getData();
        }
        
        @Override
        public int nextIndex()
        {
            return previousIndex + 1;
        }
        
        @Override
        public E previous()
        {
            if(previous != head) {
                previousIndex--;
            }
            return previous.getData();
        }
        
        @Override
        public int previousIndex()
        {
            return previousIndex;
        }
        
        @Override
        public void remove()
        {
            throw new UnsupportedOperationException();
        }
        
        @Override
        public void set(E e)
        {
            throw new UnsupportedOperationException();
        }
        
        public E setNext(E element)
        {
            checkState(previous.next != tail);
            Node old = previous.next;
            new Node(element, old);
            return old.getData();
        }
        
        private void removePrevious()
        {
            if(previous != head && previous != tail) {
                previous.remove();
            }
        }
    }
}
